import mx.xpath.XPathAPI;

import org.molamil.acto.core.Logger;
import org.molamil.acto.core.ParserError;
import org.molamil.acto.props.TypeParser;
import org.molamil.commons.StringUtil;
import org.molamil.acto.props.PropHrefPointer;


/**
 * Holding a static method to parse from a <code>&lt;props&gt;</code> XML.
 *
 * @author Jorge Hernandez
 */
class org.molamil.acto.props.PropsParser {
	
	
	// Constants
	
	public static var ERROR_MORE_PROPS:String = "ERROR: More than 1 <props> node found at the same level: \"<{level}>\".";
	public static var ERROR_PROP_AND_PROPS:String = "ERROR: Both <props> and <prop> nodes found at the same level: \"<{level}>\".";	public static var ERROR_PROPS_AND_ITEM:String = "ERROR: Both <props> and <item> nodes found at the same level: \"<{level}>\".";
	public static var ERROR_PROP_AND_LIST:String = "ERROR: Both <prop> and <list> nodes found at the same level: \"<{level}>\".";
	public static var ERROR_LIST_AND_VALUE:String = "ERROR: Both <list> and <item> nodes found at the same level: \"<{level}>\".";	public static var ERROR_DUPLICATE_NAME:String = "ERROR: Duplicate prop name: \"{name}\".";
	public static var ERROR_MISSING_NAME:String = "ERROR: Missing prop name in props at \"<{level}>\".";
	public static var ERROR_VALUE_ATTR_AND_CHILD:String = "ERROR: Value was found both as a \"value\" attribute and as " +
														  "a children at \"{level}\".";
	public static var ERROR_HREF_AND_CHILD:String = "ERROR: Value was found both as an external \"href\" reference and as " +
														  "children at \"{level}\".";
	
	
	// Properties
	
	private static var log:Logger = Logger.getLogger("PropsParser");
	
	
	// Functions
	
	/**
	 * Static parser that analyzes the given XML blocks and creates a new object containing properties 
	 * or a single value if only one <code>&lt;prop&gt;</code> node was passed.
	 * 
	 * @param   xml Xml block containing props configuration - &lt;props&gt;
	 * @return  Parsed object with properties.
	 * @throws	org.molamil.acto.core.ParserError
	 */
	public static function parse(xml:XML):Object {
		
		log.info("parse()");
		
		// Save the level.
		var level:XMLNode = xml.firstChild;
		
		// Format the props xml.
		xml.ignoreWhite = true;
		var formatXml:XML = extractPropsXml(xml);
		
		// Create the object.
		var o:Object = new Object();
		
		// Get nodes.
		var propNodes:Array = XPathAPI.selectNodeList(formatXml.firstChild, "/props/prop");
		log.debug(propNodes.length + " props found");
		
		// Iterate through all nodes or check if the xml is just one single property with a simple value.
		if (propNodes.length > 0) {
		
			for (var i:Number = 0; i < propNodes.length; i++) {
				var propNode:XMLNode = propNodes[i];
				var name:String = propNode.attributes["name"];
				if (name == null) {
					var msg:String = StringUtil.replace(ERROR_MISSING_NAME, "{level}", level.nodeName);
					log.severe("ParserError: " + msg);
					throw new ParserError(msg);
				}
				if (o[name] != null) {
					var msg:String = StringUtil.replace(ERROR_DUPLICATE_NAME, "{name}", name);
					log.severe("ParserError: " + msg);
					throw new ParserError(msg);
				}
				log.debug("processing prop: " + name);
				o[name] = getValue(propNode);
			}
		
		} else if(xml.firstChild.nodeName == "prop") {
			
			o = getSimpleValue(xml.firstChild);
			
		}

		return o;
		
	}
	
	/**
	 * Gets and XML with format an arbitrary root and and <code>&lt;prop&gt;</code> subnodes and 
	 * returns a <code>&lt;props&gt;</code> XML.
	 * 
	 * @param   xml Xml block containing a root node and <code>&lt;prop&gt;</code> subnodes.
	 * @return  <code>&lt;props&gt;</code> XML.
	 * @throws	org.molamil.acto.core.ParserError
	 */
	public static function extractPropsXml(xml:XML):XML {
		
		// Create the input XML.
		var inXml:XML = xml;
		
		// Check whether we have a a max of 1 <props> node and no <props> and <prop> nodes mixed.
		var propsNodes:Array = XPathAPI.selectNodeList(xml.firstChild, "/" + xml.firstChild.nodeName + "/props");
		if (propsNodes.length == 1) {
			if (XPathAPI.selectNodeList(xml.firstChild, "/" + xml.firstChild.nodeName + "/prop").length == 0) {
				inXml = new XML(propsNodes[0].toString());
			} else {
				var msg:String = StringUtil.replace(ERROR_PROP_AND_PROPS, "{level}", xml.firstChild.nodeName);
				log.severe("ParserError: " + msg);
				throw new ParserError(msg);
			}
		} else if (propsNodes.length > 1) {
			var msg:String = StringUtil.replace(ERROR_MORE_PROPS, "{level}", xml.firstChild.nodeName);
			log.severe("ParserError: " + msg);
			throw new ParserError(msg);
		}
			
		// Create the new xml.
		var outXml:XML = new XML();
		var root:XMLNode = outXml.createElement("props");
		outXml.appendChild(root);
		
		// Copy all subnodes.
		var propNodes:Array = new Array();
		for (var i:Number = 0; i < inXml.firstChild.childNodes.length; i++) {
			var prop:XMLNode = inXml.firstChild.childNodes[i];
			if (prop.nodeName == "prop")
				propNodes.push(prop);
		}
		for (var i:Number = 0; i < propNodes.length; i++)
			root.appendChild(propNodes[i]);
		
		return outXml;
		
	}
	
	private static function getValue(node:XMLNode):Object {
		
		var value:Object = null;
		
		var subpropsNode:XMLNode = XPathAPI.selectSingleNode(node, "/" + node.nodeName + "/props");
		var subpropNodes:Array = XPathAPI.selectNodeList(node, "/" + node.nodeName + "/prop");
		
		var sublistNode:XMLNode = XPathAPI.selectSingleNode(node, "/" + node.nodeName + "/list");
		var sublistItemNodes:Array = XPathAPI.selectNodeList(node, "/" + node.nodeName + "/item");
		var itemNodes:Array = new Array();
		if (sublistItemNodes.length == 0) {
			itemNodes = XPathAPI.selectNodeList(sublistNode, "/list/item");
		} else {
			if (sublistNode != null) {
				var msg:String = StringUtil.replace(ERROR_LIST_AND_VALUE, "{level}", node.nodeName);
				log.severe("ParserError: " + msg);
				throw new ParserError(msg);
			}
			itemNodes = sublistItemNodes;
		}
		
		if (node.attributes["href"] != null) {
			
			if (subpropsNode != null || subpropNodes.length > 0 || itemNodes.length > 0) {
				var msg:String = StringUtil.replace(ERROR_HREF_AND_CHILD, "{level}", node.nodeName);
				log.severe("ParserError: " + msg);
				throw new ParserError(msg);
			}
			
			var name:String = node.attributes["name"];			var href:String = node.attributes["href"];
			
			value = new PropHrefPointer(name, href);
					} else if (subpropsNode != null || subpropNodes.length > 0) {
			
			if (node.attributes["value"] != null) {
				var msg:String = StringUtil.replace(ERROR_VALUE_ATTR_AND_CHILD, "{level}", node.nodeName);
				log.severe("ParserError: " + msg);
				throw new ParserError(msg);
			}
			
			if (sublistNode != null) {
				var msg:String = StringUtil.replace(ERROR_PROP_AND_LIST, "{level}", node.nodeName);
				log.severe("ParserError: " + msg);
				throw new ParserError(msg);
			}
			
			if (itemNodes.length != 0) {
				var msg:String = StringUtil.replace(ERROR_PROPS_AND_ITEM, "{level}", node.nodeName);
				log.severe("ParserError: " + msg);
				throw new ParserError(msg);
			}
			
			value = parse(new XML(node.toString()));
			
		} else if (itemNodes.length > 0) {
			
			if (node.attributes["value"] != null) {
				var msg:String = StringUtil.replace(ERROR_VALUE_ATTR_AND_CHILD, "{level}", node.nodeName);
				log.severe("ParserError: " + msg);
				throw new ParserError(msg);
			}
			
			value = new Array();
			for (var j:Number = 0; j < itemNodes.length; j++)
				value.push(getValue(itemNodes[j]));

		} else {
			
			value = getSimpleValue(node);
			
		}
		
		return value;
		
	}
	
	private static function getSimpleValue(node:XMLNode):Object {
		var value:String;
		if (node.attributes["value"] != null && node.firstChild.nodeValue != null) {
			var msg:String = StringUtil.replace(ERROR_VALUE_ATTR_AND_CHILD, "{node}", node.nodeName);
			log.severe("ParserError: " + msg);
			throw new ParserError(msg);
		} else if (node.attributes["value"] != null) {
			value = node.attributes["value"];
		} else if (node.firstChild.nodeValue != null) {
			value = node.firstChild.nodeValue;
		}
		return TypeParser.parse(value);
	}
	
	
}